﻿using System;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.Kernel.Storage.Transactions;

namespace Tests.O1.Kernel.Storage.Transactions
{
    [TestClass]
    public class TxTests
    {
        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInitializedWithMaxValueDeletedByDefault()
        {
            new Tx(1234).DeletedTxId.Should().Be(ulong.MaxValue);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInitializedWithDeletedFalse()
        {
            new Tx(1234).Deleted.Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxGetHashcodeIsConsistent()
        {
            ulong addTx = 1234;
            var tx = new Tx(addTx, ulong.MaxValue);
            tx.GetHashCode().Should().Be(tx.GetHashCode());
            tx.GetHashCode().Should().NotBe(new Tx().GetHashCode());
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxObjectEqualsIsFalseComparedToNull()
        {
            ((object)new Tx()).Equals(null).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxObjectEqualsIsFalseComparedToNonUlongOrTx()
        {
            ((object)new Tx(12345, ulong.MaxValue)).Equals("12345").Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxObjectEqualsWhenBoxedMatchesEquals()
        {
            ulong addTx = 1234;
            var tx = new Tx(addTx, ulong.MaxValue);
            var boxed = (object)tx;

            boxed.Equals(addTx).Should().Be(addTx == tx);
            boxed.Equals(tx).Should().Be(addTx == tx);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxPendingIsTrueWhenAddIsPending()
        {
            var tx = new Tx(12345, ulong.MaxValue);
            tx.Pending.Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxPendingIsFalseWhenAddIsCommitted()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, ulong.MaxValue);
            tx.Pending.Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxPendingIsTrueWhenDeleteIsPending()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, 8888);
            tx.Pending.Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxPendingIsFalseWhenDeleteIsCommitted()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deleteTx = ((ulong)(1238)).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, deleteTx);
            tx.Pending.Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsTrueForPendingAdd()
        {
            ulong addTx = 1234;
            var tx1 = new Tx(addTx, ulong.MaxValue);
            var tx2 = new Tx(addTx, ulong.MaxValue);
            tx1.Equals(tx2).Should().BeTrue();
            tx2.Equals(tx1).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsTrueForCommittedAdd()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            var tx1 = new Tx(addTx, ulong.MaxValue);
            var tx2 = new Tx(addTx, ulong.MaxValue);
            tx1.Equals(tx2).Should().BeTrue();
            tx2.Equals(tx1).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsTrueForPendingDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = (ulong)45567;
            var tx1 = new Tx(addTx, deletedTx);
            var tx2 = new Tx(addTx, deletedTx);
            tx1.Equals(tx2).Should().BeTrue();
            tx2.Equals(tx1).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsTrueForCommittedDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = ((ulong)45567).EmbedStatus(TxStatus.Committed);
            var tx1 = new Tx(addTx, deletedTx);
            var tx2 = new Tx(addTx, deletedTx);
            tx1.Equals(tx2).Should().BeTrue();
            tx2.Equals(tx1).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsFalseForPendingAdd()
        {
            ulong addTx1 = 1234;
            ulong addTx2 = 1236;
            var tx1 = new Tx(addTx1, ulong.MaxValue);
            var tx2 = new Tx(addTx2, ulong.MaxValue);
            tx1.Equals(tx2).Should().BeFalse();
            tx2.Equals(tx1).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsFalseForCommittedAdd()
        {
            ulong addTx1 = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong addTx2 = ((ulong)(1238)).EmbedStatus(TxStatus.Committed);
            var tx1 = new Tx(addTx1, ulong.MaxValue);
            var tx2 = new Tx(addTx2, ulong.MaxValue);
            tx1.Equals(tx2).Should().BeFalse();
            tx2.Equals(tx1).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsFalseForPendingDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx1 = (ulong)45567;
            ulong deletedTx2 = (ulong)45569;
            var tx1 = new Tx(addTx, deletedTx1);
            var tx2 = new Tx(addTx, deletedTx2);
            tx1.Equals(tx2).Should().BeFalse();
            tx2.Equals(tx1).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualsIsFalseForCommittedDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx1 = ((ulong)45567).EmbedStatus(TxStatus.Committed);
            ulong deletedTx2 = ((ulong)45588).EmbedStatus(TxStatus.Committed);
            var tx1 = new Tx(addTx, deletedTx1);
            var tx2 = new Tx(addTx, deletedTx2);
            tx1.Equals(tx2).Should().BeFalse();
            tx2.Equals(tx1).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualityIsAsExpectedForPendingAdd()
        {
            ulong addTx = 1234;
            var tx = new Tx(addTx, ulong.MaxValue);
            (addTx == tx).Should().BeTrue();
            (tx == addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualityIsAsExpectedForCommittedAdd()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, ulong.MaxValue);
            (addTx == tx).Should().BeTrue();
            (tx == addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualityIsAsExpectedForPendingDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = (ulong)45567;
            var tx = new Tx(addTx, deletedTx);
            (addTx == tx).Should().BeFalse();
            (tx == addTx).Should().BeFalse();
            (deletedTx == tx).Should().BeTrue();
            (tx == deletedTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxEqualityIsAsExpectedForCommittedDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = ((ulong)45567).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, deletedTx);
            (addTx == tx).Should().BeFalse();
            (tx == addTx).Should().BeFalse();
            (deletedTx == tx).Should().BeTrue();
            (tx == deletedTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInequalityIsAsExpectedForPendingAdd()
        {
            ulong addTx = 1234;
            ulong addTx1 = 1235;
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx != tx).Should().BeTrue();
            (tx != addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInequalityIsAsExpectedForCommittedAdd()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong addTx1 = ((ulong)(1235)).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx != tx).Should().BeTrue();
            (tx != addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInequalityIsAsExpectedForPendingDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = (ulong)45567;
            ulong deletedTx1 = (ulong)45568;
            var tx = new Tx(addTx, deletedTx1);
            (addTx != tx).Should().BeTrue();
            (tx != addTx).Should().BeTrue();
            (deletedTx != tx).Should().BeTrue();
            (tx != deletedTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxInequalityIsAsExpectedForCommittedDelete()
        {
            ulong addTx = ((ulong)(1234)).EmbedStatus(TxStatus.Committed);
            ulong deletedTx = ((ulong)45567).EmbedStatus(TxStatus.Committed);
            ulong deletedTx1 = ((ulong)45568).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx, deletedTx1);
            (addTx != tx).Should().BeTrue();
            (tx != addTx).Should().BeTrue();
            (deletedTx != tx).Should().BeTrue();
            (tx != deletedTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxGreateWhenAddIsPendingAndGreaterThanOtherAdd()
        {
            ulong addTx = 1234;
            ulong addTx1 = 1235;
            var tx = new Tx(addTx, ulong.MaxValue);
            (addTx1 >  tx).Should().BeTrue();
            (tx < addTx1).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxLessWhenAddIsPendingAndLessThanOtherAdd()
        {
            ulong addTx = 1234;
            ulong addTx1 = 1235;
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx < tx).Should().BeTrue();
            (tx > addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxLessThanEqualToWhenAddIsPendingAndLessThanOtherAdd()
        {
            ulong addTx = 1234;
            ulong addTx1 = 1235;
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx <= tx).Should().BeTrue();
            (tx >= addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxLessThanEqualToWhenAddIsCommittedAndLessThanOtherAdd()
        {
            ulong addTx = (1234ul).EmbedStatus(TxStatus.Committed);
            ulong addTx1 = (1235ul).EmbedStatus(TxStatus.Committed);
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx <= tx).Should().BeTrue();
            (tx >= addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxLessThanEqualToWhenAddIsCommittedAndLessThanOtherUncommittedAdd()
        {
            ulong addTx = (1234ul).EmbedStatus(TxStatus.Committed);
            ulong addTx1 = (1235ul).EmbedStatus(TxStatus.Pending);
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx <= tx).Should().BeTrue();
            (tx >= addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxGreaterThanEqualToWhenAddIsCommittedAndGreaterThanOtherUncommittedAdd()
        {
            ulong addTx = (1236ul).EmbedStatus(TxStatus.Committed);
            ulong addTx1 = (1235ul).EmbedStatus(TxStatus.Pending);
            var tx = new Tx(addTx1, ulong.MaxValue);
            (addTx >= tx).Should().BeTrue();
            (tx <= addTx).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxCompareToReturns0ForEqualityWhenValueIsUncommitted()
        {
            ulong addTx = (1235ul).EmbedStatus(TxStatus.Pending);
            ulong addTx1 = (1235ul).EmbedStatus(TxStatus.Pending);
            var tx = new Tx(addTx1, ulong.MaxValue);
            tx.CompareTo(addTx).Should().Be(0);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxCompareToReturns0ForEqualityWhenValueIsCommitted()
        {
            // This is the same TX ID, just in a different state:
            ulong addTx = (1235ul).EmbedStatus(TxStatus.Committed);
            ulong addTx1 = (1235ul).EmbedStatus(TxStatus.Pending);
            var tx = new Tx(addTx1, ulong.MaxValue);
            tx.CompareTo(addTx).Should().Be(0);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void TxCanWriteAndBeReadCorrectly()
        {
            var tx = new Tx(1235ul, ulong.MaxValue);
            var buffer = new byte[Tx.Size];
            tx.Write(buffer, 0);
            var tx2 = Tx.Read(buffer, 0);

            tx2.TxTime.Should().Be(tx.TxTime);
            tx2.AddedTxId.Should().Be(tx.AddedTxId);
            tx2.DeletedTxId.Should().Be(tx.DeletedTxId);
        }
    }
}
